<?php
/**
 * User: wangxiaoxiao
 * Date: 2019/10/9
 * Time: 15:22
 * Description:
 */

namespace api\behaviors;

use common\libs\Logger;
use Yii;
use yii\base\Behavior;
use yii\base\Exception;
use yii\web\Controller;

class RedisBehavior extends Behavior
{
    const EXPIRE = 3600;//秒数
    const LIMIT = 20;//次数

    const MINUTE = 60;
    const MINUTE_LIMIT = 50;
//    const MINUTE = 10;
//    const MINUTE_LIMIT = 3;

    const HOUR = 3600;
    const HOUR_LIMIT = 200;
//    const HOUR   = 60;
//    const HOUR_LIMIT   = 6;

    public function events()
    {
        return [
            Controller::EVENT_BEFORE_ACTION => 'clientCount',
        ];
    }


    public function clientCount()
    {
        $response = Yii::$app->response;
        //当前用户信息(部分接口没有token,暂时取消)
        //$userData = Yii::$app->user->identity;
        //if (empty($userData)) {
        //    throw new Exception('非法请求,请登录');
        //}
        //用户id+ip作为key
        //$key = $userData->id . '-' . $this->getIp();
        $key = Yii::$app->cache->keyPrefix . $this->getIp();

        //debug关闭以后开启限流规则
        if (Yii::$app->cache instanceof \yii\redis\Cache) {
            if (!YII_DEBUG) {
                //PLAN A 分钟/小时级限流方式
                $r = $this->preCheckFrequency($key);
                //PLAN B 时间内可以访问多少次
                //$this->AccessAllowedCount($key, $userData);
                if (!$r['r']) {
                    $response->statusCode = $r['code'];
                }
            }
        }
    }


    /**
     * User: wangxiaoxiao
     * Description: 分钟/小时级限流方式
     * @param $key
     * @return array
     */
    public function preCheckFrequency($key)
    {
        //单独配置redis,不然无法使用redis命令操作
        $redis = Yii::$app->cache->redis;
        $limitMinute = $redis->get($key .'_'. self::MINUTE) ?: 0;
        $limitHour = $redis->get($key .'_'. self::HOUR) ?: 0;

        if ($limitMinute >= self::MINUTE_LIMIT && $limitHour <= self::HOUR_LIMIT) {//分钟级限流
            $lifeTime = $redis->ttl($key .'_'. self::MINUTE);
            $debugInfo = [
                'ip' => $this->getIp()
            ];
            Logger::warning($debugInfo, '触发分钟级限流');
//            throw new Exception(60 . "秒内只允许访问" . 3 . "次!请" . $lifeTime . "秒之后再尝试");
            return ['r' => false, 'code' => 429];
        } else if ($limitHour >= self::HOUR_LIMIT) {//小时级限流
            $lifeTime = $redis->ttl($key .'_'. self::HOUR);
            $debugInfo = [
                'ip' => $this->getIp()
            ];
            Logger::warning($debugInfo, '触发小时级限流');
//            throw new Exception(60 . "秒内只允许访问" . 3 . "次!请" . $lifeTime . "秒之后再尝试");
            return ['r' => false, 'code' => 430];
        } else {
            $redis->setex($key .'_'. self::MINUTE, self::MINUTE, $limitMinute + 1);
            $redis->setex($key .'_'. self::HOUR, self::HOUR, $limitHour + 1);
        }
        return ['r' => true];
    }


    /**
     * User: wangxiaoxiao
     * Description: 时间内可以访问多少次
     * @param $key
     * @param $user
     * @throws Exception
     */
    public function AccessAllowedCount($key, $user)
    {
        //本地环境使用文件缓存,正式环境请切换
        $redis = Yii::$app->redis;
        $limit = self::LIMIT;
        $r = $redis->get($key);
        if ($r) {
            $redis->incr($key);
            $count = $redis->get($key);
            if ($count > $limit) {
                $debugInfo = [
                    'userId' => $user->id,
                    'ip' => $this->getIp(),
                    'count' => $count
                ];
                Logger::error($debugInfo, self::EXPIRE . '秒内,该用户访问超过' . $limit . '次');
                throw new Exception('已经超出了当前用户访问次数,请等待' . self::EXPIRE . '秒以后操作');
            }
        } else {
            $redis->incr($key);
            $redis->expire($key, self::EXPIRE);
        }
    }


    /**
     * User: wangxiaoxiao
     * Description: 获取用户真实ip地址
     */
    public function getIp()
    {
        static $ip;
        if (isset($_SERVER)) {
            if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
                $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
            } else if (isset($_SERVER['HTTP_CLIENT_IP'])) {
                $ip = $_SERVER['HTTP_CLIENT_IP'];
            } else {
                $ip = $_SERVER['REMOTE_ADDR'];
            }
        } else {
            if (getenv('HTTP_X_FORWARDED_FOR')) {
                $ip = getenv('HTTP_X_FORWARDED_FOR');
            } else if (getenv('HTTP_CLIENT_IP')) {
                $ip = getenv('HTTP_CLIENT_IP');
            } else {
                $ip = getenv('REMOTE_ADDR');
            }
        }
        return $ip;
    }
}